package com.ruoyi.security.service.backtest.gridtrading.service.impl;

import com.alibaba.fastjson.JSON;
import com.ruoyi.security.convert.SecurityGridTradingBacktestResultDtoConvert;
import com.ruoyi.security.dto.backtest.SecurityGridTradingBacktestParamDto;
import com.ruoyi.security.domain.SecurityDayMinuteTrade;
import com.ruoyi.security.dto.SecurityDayMinuteTradeDto;
import com.ruoyi.security.domain.SecurityTradingCalendar;
import com.ruoyi.security.dto.backtest.SecurityGridTradingBacktestResultDto;
import com.ruoyi.security.dto.backtest.SecurityGridTradingBacktestResultTradingStatisticsDto;
import com.ruoyi.security.service.ISecurityDayMinuteTradeService;
import com.ruoyi.security.service.ISecurityTradingCalendarService;
import com.ruoyi.security.service.backtest.gridtrading.context.BacktestContext;
import com.ruoyi.security.service.backtest.gridtrading.processor.TradeProcessorServiceFactory;
import com.ruoyi.security.service.backtest.gridtrading.service.ISecurityGridTradingBacktestHistoryService;
import com.ruoyi.security.service.backtest.gridtrading.service.ISecurityGridTradingBacktestService;
import com.ruoyi.security.service.backtest.gridtrading.strategy.RiseFallTypeCalcStrategy;
import com.ruoyi.security.service.backtest.gridtrading.strategy.MultipleCalculationStrategyFactory;
import com.ruoyi.security.util.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 回测服务实现类
 * @author yeoman
 * @date 2025/2/4 21:38
 */
@Slf4j
@Service
public class SecurityGridTradingBacktestServiceImpl implements ISecurityGridTradingBacktestService {

    @Resource
    private ISecurityDayMinuteTradeService securityDayMinuteTradeService;
    
    @Resource
    private ISecurityTradingCalendarService securityTradingCalendarService;
    
    @Resource
    private MultipleCalculationStrategyFactory multipleCalculationStrategyFactory;

    @Resource
    private TradeProcessorServiceFactory tradeProcessorServiceFactory;

    @Resource
    private ISecurityGridTradingBacktestHistoryService gridTradingBacktestHistoryService;

    @Override
    public SecurityGridTradingBacktestResultDto backtest(SecurityGridTradingBacktestParamDto backtestParamDto) {
        // 1. 查询交易数据
        List<SecurityDayMinuteTrade> tradeList = queryTradeData(backtestParamDto.getSymbol(),
                backtestParamDto.getBacktestPeriod().getStartDate(), backtestParamDto.getBacktestPeriod().getEndDate());
        if (tradeList.isEmpty()) {
            log.info("未查询到数据");
            return null;
        }

        // 2. 获取涨跌类型计算策略计算策略（使用工厂模式）
        RiseFallTypeCalcStrategy riseFallTypeCalcStrategy = multipleCalculationStrategyFactory.getStrategy(backtestParamDto.getTriggerCondition().getRiseFallType());
        
        // 3. 初始化回测上下文
        BacktestContext context = new BacktestContext(backtestParamDto, riseFallTypeCalcStrategy);

        // 4. 按日期分组并排序
        Map<Date, List<SecurityDayMinuteTrade>> groupByDate = tradeList.stream()
                .collect(Collectors.groupingBy(SecurityDayMinuteTrade::getDate));
        List<Map.Entry<Date, List<SecurityDayMinuteTrade>>> sortedByDate = groupByDate.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .collect(Collectors.toList());

        SecurityGridTradingBacktestResultDto backtestResultDto = new SecurityGridTradingBacktestResultDto();
        // 5. 执行回测
        for (Map.Entry<Date, List<SecurityDayMinuteTrade>> dateListEntry : sortedByDate) {
            String date = DateUtil.parseDateToStr(dateListEntry.getKey());

            SecurityGridTradingBacktestResultDto.DayDetail dayDetail = new SecurityGridTradingBacktestResultDto.DayDetail();
            dayDetail.setDate(date);

            processDayTrading(date, dateListEntry.getValue(), context, dayDetail);
            backtestResultDto.getDayDetailList().add(dayDetail);
        }

        // 6. 更新回测结果对象
        updateBacktestResultDto(backtestResultDto, context, backtestParamDto);

        // 7. 打印回测结果
        logResult(backtestResultDto);

        // 8. 插入回测结果历史记录
        gridTradingBacktestHistoryService.insert(backtestResultDto);

        return backtestResultDto;
    }
    
    /**
     * 查询交易数据
     */
    private List<SecurityDayMinuteTrade> queryTradeData(String symbol, String startDate, String endDate) {
        // 1. 查询日期范围内的交易日
        List<SecurityTradingCalendar> tradingDays = getTradingDays(startDate, endDate);
        if (tradingDays.isEmpty()) {
            throw new RuntimeException(String.format("日期范围[%s, %s]内没有交易日", startDate, endDate));
        }
        
        // 2. 查询交易数据
        SecurityDayMinuteTradeDto dto = new SecurityDayMinuteTradeDto();
        dto.setSymbol(symbol);
        dto.setStratDate(startDate);
        dto.setEndDate(endDate);
        List<SecurityDayMinuteTrade> tradeList = securityDayMinuteTradeService.list(dto);
        
        // 3. 校验数据完整性
        List<String> invalidDates = securityDayMinuteTradeService.getInvalidTradeDates(symbol, tradingDays, tradeList);
        
        // 如果有缺失或不完整的数据，抛出异常
        if (!invalidDates.isEmpty()) {
            String errorMsg = String.format("证券[%s]数据校验失败，缺失数据的交易日：%s", 
                    symbol, String.join(", ", invalidDates));
            throw new RuntimeException(errorMsg);
        }
        
        log.info("证券[{}]数据校验通过，共{}个交易日，每个交易日数据完整", 
                symbol, tradingDays.size());
        
        return tradeList;
    }
    
    /**
     * 获取日期范围内的交易日（带完整性校验）
     */
    private List<SecurityTradingCalendar> getTradingDays(String startDate, String endDate) {
        Date start = DateUtil.parseDate(startDate);
        Date end = DateUtil.parseDate(endDate);
        if (start == null || end == null) {
            throw new RuntimeException(String.format("日期格式错误，开始日期：%s，结束日期：%s", startDate, endDate));
        }
        
        // 使用带校验的方法查询日期范围内的交易日
        return securityTradingCalendarService.selectTradingDays(start, end);
    }

    /**
     * 处理单日交易
     */
    private void processDayTrading(String date, List<SecurityDayMinuteTrade> tradeList, BacktestContext context, SecurityGridTradingBacktestResultDto.DayDetail dayDetail) {
        // 按时间排序
        tradeList.sort(Comparator.comparing(SecurityDayMinuteTrade::getTime));

        context.initDayContext(date);

        for (SecurityDayMinuteTrade trade : tradeList) {
            context.setMinuteContext(trade);
            if (!context.getBacktestVariable().getTradeFlag()) {
                continue;
            }
            tradeProcessorServiceFactory.getTradeProcessorService(context).process(context);
            addTradeDetail(dayDetail.getDetails(), context);
        }

        // 更新当日收盘收益相关数据
        // 当日收盘价
        BigDecimal closePrice = tradeList.get(tradeList.size() - 1).getPrice();
        // 更新收益相关指标
        context.updateProfit(closePrice);

        // 更新空仓日期
        context.updateEmptyPositionDate();

        // 更新日交易Dto
        updateDayTransactionDetailResDto(dayDetail, context);

        //log.info("{} 日共买入 {} 次，卖出 {} 次，当前持仓: {}股，当前基准价: {} 元，交易手续费: {}，市值: {} 元，可用资金: {} 元，总资产: {}，累计盈亏: {} 元，累计盈亏率: {}%",
        //        date, buyCountOfDay, sellCountOfDay, context.getPosition(), context.getBenchmarkPrice(),
        //        tradeChargeOfDay, context.getTotalTradeCharge(), context.getAvailableAmount(), context.getTotalAssets(), context.getTotalProfit(), context.getTotalProfitRate());
    }

    private void addTradeDetail(List<SecurityGridTradingBacktestResultDto.DayDetail.Detail> details, BacktestContext context) {
        SecurityGridTradingBacktestResultDto.DayDetail.Detail detail = SecurityGridTradingBacktestResultDtoConvert.INSTANCE.toSecurityGridTradingBacktestResultDtoDetail(context.getBacktestVariable().getTradeInfo());
        detail.setPosition(context.getBacktestVariable().getPosition());
        details.add(detail);
    }

    private void wrapperCommonTradingStatistics(SecurityGridTradingBacktestResultTradingStatisticsDto tradingStatisticsDto, BacktestContext.BacktestVariable backtestVariable) {
        SecurityGridTradingBacktestResultDtoConvert.INSTANCE.updateCommonSecurityGridTradingBacktestResultDtoBacktestVariable(backtestVariable, tradingStatisticsDto);
        SecurityGridTradingBacktestResultDtoConvert.INSTANCE.updateCommonSecurityGridTradingBacktestResultDtoProfit(backtestVariable.getProfitInfo(), tradingStatisticsDto);
    }

    private void wrapperDayTradingStatistics(SecurityGridTradingBacktestResultDto.DayDetail dayDetail, BacktestContext context) {
        // 保存当前上下文快照，深拷贝，避免引用问题
        BacktestContext.BacktestVariable backtestVariableSnapshot = JSON.parseObject(JSON.toJSONString(context.getBacktestVariable()), BacktestContext.BacktestVariable.class);
        BacktestContext.BacktestVariable.DayTradeInfo dayTradeInfo = backtestVariableSnapshot.getDayTradeInfo();
        SecurityGridTradingBacktestResultDtoConvert.INSTANCE.updateDaySecurityGridTradingBacktestResultDtoTradeDetails(dayTradeInfo, dayDetail);

        wrapperCommonTradingStatistics(dayDetail, backtestVariableSnapshot);
    }

    private void wrapperTotalTradingStatisticsDto(SecurityGridTradingBacktestResultDto resultDto, BacktestContext context) {
        BacktestContext.BacktestVariable.TotalTradeInfo totalTradeInfo = context.getBacktestVariable().getTotalTradeInfo();
        SecurityGridTradingBacktestResultDtoConvert.INSTANCE.updateTotalSecurityGridTradingBacktestResultDtoTradeDetails(totalTradeInfo, resultDto);

        SecurityGridTradingBacktestResultDtoConvert.INSTANCE.updateCommonSecurityGridTradingBacktestResultDtoBacktestParam(context.getBacktestParam(), resultDto);
        wrapperCommonTradingStatistics(resultDto, context.getBacktestVariable());

        List<SecurityGridTradingBacktestResultDto.DayDetail> dayDetailList = resultDto.getDayDetailList();

        // 最大回撤金额及周期
        calculateMaxDrawdownAmountPeriod(resultDto, dayDetailList);

        // 最大回撤（%）- 当前投入
        List<BigDecimal> profitRateByCurrentInvestmentList = dayDetailList.stream().map(SecurityGridTradingBacktestResultDto.DayDetail::getTotalProfitRateByCurrentInvestment).collect(Collectors.toList());
        resultDto.setMaxDrawdownRatioByCurrentInvestment(calculateMaxDrawdownRatio(profitRateByCurrentInvestmentList));

        // 最大回撤（%）- 最大投入
        List<BigDecimal> profitRateByMaxInvestmentList = dayDetailList.stream().map(SecurityGridTradingBacktestResultDto.DayDetail::getTotalProfitRateByMaxInvestment).collect(Collectors.toList());
        resultDto.setMaxDrawdownRatioByMaxInvestment(calculateMaxDrawdownRatio(profitRateByMaxInvestmentList));

        // 最大回撤（%）- 期初总资产
        List<BigDecimal> profitRateByInitialTotalAssetList = dayDetailList.stream().map(SecurityGridTradingBacktestResultDto.DayDetail::getTotalProfitRateByInitialTotalAssets).collect(Collectors.toList());
        resultDto.setMaxDrawdownRatioByInitialTotalAssets(calculateMaxDrawdownRatio(profitRateByInitialTotalAssetList));

        // 通过交易开始日期和结束日期计算持仓天数
        String startDate = resultDto.getStartDate();
        String endDate = resultDto.getEndDate();
        int totalDays = (int) (LocalDate.parse(endDate).toEpochDay() - LocalDate.parse(startDate).toEpochDay() + 1);
        resultDto.setPositionDays(totalDays - context.getEmptyPositionDateList().size());
        resultDto.setEmptyPositionDays(context.getEmptyPositionDateList().size());
        resultDto.setEmptyPositionDateList(context.getEmptyPositionDateList());
    }

    private void updateDayTransactionDetailResDto(SecurityGridTradingBacktestResultDto.DayDetail dayDetail, BacktestContext context) {
        dayDetail.setDate(context.getBacktestVariable().getDate());
        wrapperDayTradingStatistics(dayDetail, context);
    }

    /**
     * 计算最大回撤金额及周期
     */
    private void calculateMaxDrawdownAmountPeriod(SecurityGridTradingBacktestResultTradingStatisticsDto tradingStatisticsDto, List<SecurityGridTradingBacktestResultDto.DayDetail> dayDetailList) {
        // 找到利润最大的l对象
        SecurityGridTradingBacktestResultDto.DayDetail maxProfitRateDayDetail = dayDetailList.stream().max(Comparator.comparing(SecurityGridTradingBacktestResultDto.DayDetail::getTotalProfit)).orElse(null);
        // 找到最大回撤结束日期
        SecurityGridTradingBacktestResultDto.DayDetail maxDrawdownEndDayDetail = dayDetailList.stream().min(Comparator.comparing(SecurityGridTradingBacktestResultDto.DayDetail::getTotalProfit)).orElse(null);
        tradingStatisticsDto.setMaxDrawdownStartDate(maxProfitRateDayDetail.getDate());
        tradingStatisticsDto.setMaxDrawdownEndDate(maxDrawdownEndDayDetail.getDate());
        tradingStatisticsDto.setMaxDrawdownAmount(maxDrawdownEndDayDetail.getTotalProfit().subtract(maxProfitRateDayDetail.getTotalProfit()));
    }

    /**
     * 计算最大回撤(%) = (最大利润率 - 最小利润率) / 最大利润率
     * @param profitRateList 最大利润率列表
     */
    private BigDecimal calculateMaxDrawdownRatio(List<BigDecimal> profitRateList) {
        BigDecimal maxProfitRate = profitRateList.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
        BigDecimal minProfitRate = profitRateList.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
        return maxProfitRate.subtract(minProfitRate).divide(maxProfitRate, 2, RoundingMode.HALF_UP);
    }

    private void updateBacktestResultDto(SecurityGridTradingBacktestResultDto resultDto, BacktestContext context, SecurityGridTradingBacktestParamDto backtestParamDto) {
        SecurityGridTradingBacktestResultDtoConvert.INSTANCE.updateSecurityGridTradingBacktestResultDto(context, resultDto);
        wrapperTotalTradingStatisticsDto(resultDto, context);
    }
    
    /**
     * 输出回测结果
     */
    private void logResult(SecurityGridTradingBacktestResultDto result) {
        log.info("\n ------------------------------------------------------------------------------------------------------------" +
                        "\n\t\t\t\t以 初始基准价 {} 元，初始持仓 {} 手，初始市值 {} 元回测" +
                        "\n\n期初总资产: {} 元，期末总资产: {} 元，累计盈亏: {} 元，一直持有收益:{} 元，累计盈亏率（当前投入）: {}%， 一直持有收益率：{}%，超额收益率（当前投入）：{}%" +
                        "\n 共买入 {} 次，卖出 {} 次，当前持仓: {} 手，交易手续费: {}，市值: {} 元，可用资金: {} 元" +
                        "\n\n 持仓天数{}，空仓天数{} ({})" +
                        "\n ------------------------------------------------------------------------------------------------------------",
                result.getInitialBenchmarkPrice(),
                result.getInitialPosition(),
                result.getInitialMarketValue(),
                result.getInitialTotalAssets(),
                result.getTotalAssets(),
                result.getTotalProfit(),
                result.getAlwaysHoldProfit(),
                result.getTotalProfitRateByCurrentInvestment(),
                result.getAlwaysHoldProfitRate(),
                result.getExcessProfitRateByCurrentInvestment(),
                result.getBuyCount(),
                result.getSellCount(),
                result.getPosition(),
                result.getTradeChargeAmount(),
                result.getMarketValue(),
                result.getAvailableAmount(),
                result.getPositionDays(),
                result.getEmptyPositionDays(),
                result.getEmptyPositionDateList().toArray());
    }
    
}
